package validjson;

import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.ObjectMapper;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Schema {
    private Map<String, Object> schemaMap;
    private Map<String, Object> rootSchema;

    public Schema(Map<String, Object> schemaMap) {
        this.schemaMap = schemaMap;
    }

    public Schema(URI uri) throws IOException {
        InputStream schemaStream = null;

        try {
            ObjectMapper mapper = new ObjectMapper();
            schemaStream = uri.toURL().openStream();
            this.schemaMap = mapper.readValue(schemaStream, HashMap.class);
        } catch (JsonParseException e) {
            throw new IOException("Error parsing '" + uri + "': " + e.getMessage(), e);
        } finally {
            tryToClose(schemaStream);
        }
    }

    public Schema(Map<String, Object> schemaMap, Map<String, Object> rootSchema) {
        this.schemaMap = schemaMap;
        this.rootSchema = rootSchema;
    }

    private static void tryToClose(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException e) {
                //do nothing
            }
        }
    }

    public Schema getSubSchema(String key) throws IOException {
        Map<String, Object> value = (Map<String, Object>) schemaMap.get(key);
        if (value == null) {
            return null;
        }
        if (value.containsKey("$ref")) {
            try {
                String[] splitSchemaReference = ((String)value.get("$ref")).split("\\.");
                Schema schemaReference = "#".equals(splitSchemaReference[0]) ? new Schema(rootSchema) : new Schema(new URI(splitSchemaReference[0] + ".json-schema"));
                for (int i = 1; i < splitSchemaReference.length; i++) {
                    String schemaReferenceToken = splitSchemaReference[i];
                    schemaReference = schemaReference.getSubSchema(schemaReferenceToken);
                }
                return schemaReference;
            } catch (URISyntaxException e) {
                throw new IOException("Cannot construct URI", e);
            }
        }
        return new Schema(value, rootSchema == null ? schemaMap : rootSchema);
    }

    public Object getValue(String key) {
        return schemaMap.get(key);
    }

    public List<String> getMandatoryPropertyNames() {
        List<String> mandatoryPropertyNames = new ArrayList<String>();
        if (schemaMap != null) {
            for (String propertyName : schemaMap.keySet()) {
                Map<String, Object> propertyDetails = (Map<String, Object>) schemaMap.get(propertyName);
                if (!propertyDetails.containsKey("optional") || !(Boolean) propertyDetails.get("optional")) {
                    mandatoryPropertyNames.add(propertyName);
                }
            }
        }
        return mandatoryPropertyNames;
    }

    public boolean containsKey(String propertyName) {
        return schemaMap != null && schemaMap.containsKey(propertyName);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Schema)) return false;

        Schema schema = (Schema) o;

        if (rootSchema != null ? !rootSchema.equals(schema.rootSchema) : schema.rootSchema != null) return false;
        if (schemaMap != null ? !schemaMap.equals(schema.schemaMap) : schema.schemaMap != null) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = schemaMap != null ? schemaMap.hashCode() : 0;
        result = 31 * result + (rootSchema != null ? rootSchema.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return "Schema{schemaMap=" + schemaMap + ", rootSchema=" + rootSchema + '}';
    }
}
